/*
Copyright (c) 2014, ebaklund
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, 
   this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, 
   this list of conditions and the following disclaimer in the documentation 
   and/or other materials provided with the distribution.

3. Neither the name of the <ORGANIZATION> nor the names of its contributors 
   may be used to endorse or promote products derived from this software 
   without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/

#include <Do/Sys/Gui/Win32/Core.h>

#include <Do/Str.h>
#include <Do/SyntacticCandy.h>
#include <Do/IroPropertyImpl.hh>
#include <Do/IrwPropertyImpl.hh>
#include <Do/Sys/Gui/Win32/Core.h>
#include <Do/Sys/Win32/UniqueHwnd.h>
#include <Do/Sys/Win32/ErrorException.h>
#include <Do/Flow/EagerSignalImpl.hh>
#include <Do/Flow/EagerSlotImpl.hh>
#include <Do/Primitives/Size2.hh>
#include <Do/Gui/PaintInfo.h>
#include <Do/Sys/Gui/Win32/PaintInfoFactory.h>

#include <Windows.h>
#include <assert.h>
#include <memory>

using Do::Sys::Win32::ErrorException;
using Do::Sys::Win32::UniqueHwnd;
using Do::Primitives::Size2u;
using Do::Gui::PaintInfo;
using Do::Flow::EagerSignalImpl;
using Do::Flow::EagerSlotImpl;
using Do::Sys::TimeSpan;


class Do::Sys::Gui::Win32::Core::Impl
  {
  private:

#pragma region PRIVATE Core::Impl data

    UniqueHwnd hwnd_;

#pragma endregion

#pragma region PRIVATE Core::Impl Static helpers

    static void registerClass(LPWSTR wnd_class_name, DWORD wnd_class_style)
      {
        assert(GetLastError() == NO_ERROR);

        WNDCLASSEXW wnd_class;
        bool class_needs_register = (GetClassInfoEx(GetModuleHandle(NULL), wnd_class_name, &wnd_class) == 0);
        SetLastError(NO_ERROR); // Clear error code generated by this test
        assert(GetLastError() == NO_ERROR);

        if (class_needs_register) 
          {
            wnd_class.cbSize        = sizeof(WNDCLASSEXW);
            wnd_class.style         = wnd_class_style;
            wnd_class.lpfnWndProc   = staticWindowProc;
            wnd_class.cbClsExtra    = 0;
            wnd_class.cbWndExtra    = sizeof(void*);
            wnd_class.hInstance     = GetModuleHandle(NULL);
            wnd_class.hIcon         = LoadIcon(NULL, IDI_WINLOGO);
            wnd_class.hCursor       = LoadCursor(NULL, IDC_ARROW);
            wnd_class.hbrBackground = NULL;
            wnd_class.lpszMenuName  = NULL;
            wnd_class.lpszClassName = wnd_class_name;
            wnd_class.hIconSm       = NULL;

            ATOM atom = RegisterClassExW(&wnd_class);
            assert(GetLastError() == NO_ERROR);
            assert(atom != 0);
          }
      }

    static UniqueHwnd createWindow(Core::Impl* self, LPWSTR lpClassName, DWORD dwStyle)
      {
        assert(GetLastError() == NO_ERROR);

        HWND hwnd = CreateWindowW (
              lpClassName,            // lpClassName
              L"",                    // lpWindowName
              dwStyle,                // dwStyle
              CW_USEDEFAULT,          // x
              CW_USEDEFAULT,          // y
              CW_USEDEFAULT,          // nWidth
              CW_USEDEFAULT,          // nHeight
              NULL,                   // hWndParent
              NULL,                   // hMenu
              GetModuleHandle(NULL),  // hInstance
              NULL);                   // lpParam
         
        assert(GetLastError() == NO_ERROR);

        SetProp(hwnd, L"CoreInstance", self);
        Impl* self2 = reinterpret_cast<Impl*>(GetProp(hwnd, L"CoreInstance"));
        assert(self == self2);
/*
        SetWindowLongPtr(hwnd, 0, reinterpret_cast<LONG_PTR>(self));
        assert(GetLastError() == NO_ERROR);

        Impl* self2 = reinterpret_cast<Impl*>(GetWindowLongPtrW(hwnd, 0));
        assert(self2 == self);
*/
#if 0
        DWORD swp_flags = 
          SWP_DEFERERASE | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOMOVE | 
          SWP_NOOWNERZORDER | SWP_NOREDRAW  | SWP_NOSIZE | SWP_NOZORDER;
        SetWindowPos(unique_hwnd.get(), HWND_TOP, 0, 0, 0, 0, swp_flags);
        DWORD e = GetLastError();
        assert(e == NO_ERROR);
#endif
        return hwnd;
      }

    static LRESULT WINAPI staticWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
      { 
//        Impl* self = reinterpret_cast<Impl*>(GetWindowLongPtrW(hWnd, 0));
        Impl* self = reinterpret_cast<Impl*>(GetProp(hWnd, L"CoreInstance"));
        if (self == nullptr)
          return DefWindowProc(hWnd, uMsg, wParam, lParam);
        
        return self->windowProc(hWnd, uMsg, wParam, lParam);
      }

#pragma endregion

#pragma region PRIVATE Property functions

    bool getVisibility()
      {
        return IsWindowVisible(hwnd()) != 0;
      }

    void setVisibility(bool visibility)
      {
        ShowWindowAsync(hwnd(), visibility ? SW_SHOW : SW_HIDE);
      }

    RECT getClientRect()
      {
        RECT rect;
        return (GetClientRect(hwnd(), &rect), rect);
      }

    RECT getWindowRect()
      {
        RECT rect;
        return (GetWindowRect(hwnd(), &rect), rect);
      }

    void setWindowRect(RECT const& rect)
      {
        UINT flags = SWP_ASYNCWINDOWPOS | SWP_NOCOPYBITS | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER;
        SetWindowPos(hwnd(), 0, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, flags);
      }

#pragma endregion

  protected:

#pragma region PROTECTED Core::Impl Virtual message handlers

    virtual LRESULT windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
      {
        assert(hwnd() == hWnd);

        switch (uMsg)
          {
          case WM_PAINT: return (handleWmPaint(uMsg, wParam, lParam), 0);
          case WM_SIZE: return (handleWmSize(uMsg, wParam, lParam), 0);
          }

        return DefWindowProc(hwnd(), uMsg, wParam, lParam);
      }
    
    virtual LRESULT handleWmPaint(UINT uMsg, WPARAM wParam, LPARAM lParam)
      {
        RECT window_rect;
        GetWindowRect(hwnd(), &window_rect);

        RECT update_rect;
        GetUpdateRect(hwnd(), &update_rect, FALSE);

        PaintInfo paint_info = PaintInfoFactory::create(window_rect, update_rect);

        paintSignal.push(paint_info);

        return DefWindowProc(hwnd(), uMsg, wParam, lParam); 
      }
    
    virtual LRESULT handleWmSize(UINT uMsg, WPARAM wParam, LPARAM lParam)
      {
        if ((wParam == SIZE_MAXIMIZED) || (wParam == SIZE_RESTORED)) {
          sizeSignal.push(Size2u(LOWORD(lParam), HIWORD(lParam)));
          return 0;
        }
        else {
          return DefWindowProc(hwnd(), uMsg, wParam, lParam); 
        }
      }

#pragma endregion

  public:

#pragma region INTERNAL Core::Impl

    Impl(LPWSTR wnd_class_name, DWORD wnd_class_style, DWORD wnd_window_style)
      {
        registerClass(wnd_class_name, wnd_class_style);
        hwnd_ = createWindow(this, wnd_class_name, wnd_window_style);
        assert(IsWindow(hwnd_.get()));

        [this] () { return this->getVisibility(); } 
          >>= this->visibility 
          >>= [this] (bool visibility) { this->setVisibility(visibility); };

        [this] () { return this->getClientRect(); } 
          >>= this->clientRect;

        [this] () { return this->getWindowRect(); } 
          >>= this->windowRect 
          >>= [this] (RECT rect) { this->setWindowRect(rect); };

        redrawSlot >>= [this] (TimeSpan const&) { redraw(); };
      }

    HWND hwnd() 
      {
        return hwnd_.get();
      }

    void redraw()
      {
        UINT flags = RDW_INTERNALPAINT | RDW_INVALIDATE | RDW_ERASENOW | RDW_UPDATENOW;
        RedrawWindow(hwnd(), nullptr, nullptr, flags);
      }

    Do::IrwPropertyImpl<bool> visibility;
    Do::IroPropertyImpl<RECT> clientRect;
    Do::IrwPropertyImpl<RECT> windowRect;

    EagerSignalImpl<Size2u>     sizeSignal;
    EagerSignalImpl<PaintInfo>  paintSignal;

    EagerSlotImpl<TimeSpan>     redrawSlot;

#pragma endregion

  };


#pragma region PUBLIC Core

Do::Sys::Gui::Win32::Core::Core(LPWSTR wnd_class_name, DWORD wnd_class_style, DWORD wnd_window_style)
  : _ (new Impl(wnd_class_name, wnd_class_style, wnd_window_style)),
    visibility(_->visibility),
    clientRect(_->clientRect),
    windowRect(_->windowRect),
    sizeSignal(_->sizeSignal),
    paintSignal(_->paintSignal),
    redrawSlot(_->redrawSlot)
  {}

Do::Sys::Gui::Win32::Core::~Core()
  {
    delete _ ;
    _ = nullptr;
  }

HWND Do::Sys::Gui::Win32::Core::hwnd() const
  {
    return _->hwnd();
  }

void Do::Sys::Gui::Win32::Core::redraw()
  {
    _->redraw();
  }
 
#pragma endregion

